/*
 * Confidential.  All Rights Reserved.
 * Copyright (C) 2008-2010 Orangatame LLC.
 * Original Author: Jason Schroeder <jason@orangatame.com>
 * Created: December 7, 2008
 */
package com.google.authenticator.blackberry;

import java.io.EOFException;
import java.util.Vector;

import com.google.authenticator.blackberry.resource.AuthenticatorResource;

import net.rim.device.api.collection.CollectionEventSource;
import net.rim.device.api.collection.CollectionListener;
import net.rim.device.api.i18n.Locale;
import net.rim.device.api.i18n.ResourceBundle;
import net.rim.device.api.synchronization.ConverterUtilities;
import net.rim.device.api.synchronization.OTASyncCapable;
import net.rim.device.api.synchronization.SyncCollection;
import net.rim.device.api.synchronization.SyncCollectionSchema;
import net.rim.device.api.synchronization.SyncConverter;
import net.rim.device.api.synchronization.SyncManager;
import net.rim.device.api.synchronization.SyncObject;
import net.rim.device.api.system.EventLogger;
import net.rim.device.api.system.RuntimeStore;
import net.rim.device.api.util.CloneableVector;
import net.rim.device.api.util.DataBuffer;
import net.rim.device.api.util.ListenerUtilities;

/**
 * Synchronization and Backup for the class.
 * 
 * Based on the "otabackuprestoredemo" demo application from the RIM JDE
 * 
 * @author Jason Schroeder <jason@orangatame.com>
 * 
 */
public class OTAConfigSync implements SyncConverter, SyncCollection, OTASyncCapable {

    private static final long AR_KEY = 0xbd6688837f35e3e4L; // com.google.authenticator.blackberry.OTAConfigSync

    private SyncCollectionSchema _schema;

    /*
     * String
     */
    private static final int FIELDTAG_EMAILADDRESS = 1;
    
    /*
     * short
     */
    private static final int FIELDTAG_TYPE = 2;
    
    /*
     * String
     */
    private static final int FIELDTAG_SECRET = 3;
    
    /*
     * int
     */
    private static final int FIELDTAG_COUNTER = 4;

    /* the id for the default (and the only) record type */
    private static final int DEFAULT_RECORD_TYPE = 1; 
    /* key fields - lets the server know which fields uniquely define a record */
    private static final int[] KEY_FIELD_IDS = new int[] {FIELDTAG_EMAILADDRESS, FIELDTAG_SECRET};

    /**
     * 
     */
    public static void init() {
        try {
            OTAConfigSync s = OTAConfigSync.getInstance();
            SyncManager mgr = SyncManager.getInstance();
            mgr.enableSynchronization(s);
            mgr.allowOTASync(s, true);
            if (mgr.isOTASyncAvailable(s, true)) {
                log("OTA Sync is available", null, EventLogger.INFORMATION);
            } else {
                log("OTA Sync is not available", null, EventLogger.INFORMATION);
            }
        } catch (Throwable t) {
            log("Exception setting up Sync", t, EventLogger.ERROR);
        }
    }

    private OTAConfigSync() {
        // set up the schema for the collection
        _schema = new SyncCollectionSchema();
        _schema.setDefaultRecordType(DEFAULT_RECORD_TYPE);
        _schema.setKeyFieldIds(DEFAULT_RECORD_TYPE, KEY_FIELD_IDS);

    }

    public static OTAConfigSync getInstance() {
        RuntimeStore rs = RuntimeStore.getRuntimeStore();
        synchronized (rs) {
            OTAConfigSync collection = (OTAConfigSync) rs.get(AR_KEY);
            if (collection == null) {
                collection = new OTAConfigSync();
                rs.put(AR_KEY, collection);
            }
            return collection;
        }
    }
    
    private static final void log(String message, Throwable t, int level) {
      System.out.println(message);
    }

    // SyncConverter methods----------
    public boolean convert(SyncObject object, DataBuffer buffer, int version) {
        if (version == getSyncVersion() && object instanceof Account) {
            Account account = (Account) object;

            ConverterUtilities.writeString(buffer, FIELDTAG_EMAILADDRESS, account.getEmail());
            ConverterUtilities.writeShort(buffer, FIELDTAG_TYPE, account.getType().value.shortValue());
            ConverterUtilities.writeString(buffer, FIELDTAG_SECRET, account.getSecret());
            ConverterUtilities.writeInt(buffer, FIELDTAG_COUNTER, account.getCounter());
            return true;
        }
        return false;
    }

    public SyncObject convert(DataBuffer data, int version, int UID) {
        try {
          Account a = new Account();
          while (data.available() > 0) {
                switch (ConverterUtilities.getType(data)) {
                case FIELDTAG_EMAILADDRESS:
                    a.setEmail(ConverterUtilities.readString(data));
                    break;
                case FIELDTAG_TYPE:
                    a.setType(AccountDb.OtpType.getEnum(ConverterUtilities.readShort(data)));
                    break;
                case FIELDTAG_SECRET:
                    a.setSecret(ConverterUtilities.readString(data));
                    break;
                case FIELDTAG_COUNTER:
                    a.setCounter(ConverterUtilities.readInt(data));
                    break;
                default:
                    ConverterUtilities.skipField(data);
                    // other fields not supported
                    break;
                }
            }
            return a;
        } catch (EOFException e) {
            log("EOF while restoring OTA", e, EventLogger.ERROR);
        }
        return null;
    }

    // SyncCollection methods----------
    public boolean addSyncObject(SyncObject object) {
      if (object instanceof Account) {
        Account account = (Account) object;
        AccountDb.update(account.getEmail(), account.getSecret(), account.getEmail(), account.getType());
        return true;
      }
      return false;
    }

    public boolean updateSyncObject(SyncObject oldObject, SyncObject newObject) {
      if (oldObject instanceof Account && newObject instanceof Account) {
        Account oldAccount = (Account) oldObject;
        Account newAccount = (Account) newObject;
        AccountDb.update(newAccount.getEmail(), newAccount.getSecret(), oldAccount.getEmail(), newAccount.getType());
        return true;
      }
      return false;
    }

    public boolean removeSyncObject(SyncObject object) {
      if (object instanceof Account) {
        Account account = (Account) object;
        if (AccountDb.nameExists(account.getEmail())) {
          AccountDb.delete(account.getEmail());
          return true;
        }
      }
      return false;
    }

    public boolean removeAllSyncObjects() {
        AccountDb.deleteAll();
        return true;
    }

    public SyncObject[] getSyncObjects() {
        SyncObject[] contactArray = new SyncObject[1];
        Vector accounts = AccountDb.getAccounts();
        for (int i = 0; i < accounts.size(); i++) {
          contactArray[i] = (Account) accounts.elementAt(i);
        }
        return contactArray;
    }

    public SyncObject getSyncObject(int uid) {
      Account account;
      Vector accounts = AccountDb.getAccounts();
      for (int i = 0; i < accounts.size(); i++) {
         account = (Account) accounts.elementAt(i);
         if (account.getUID() == uid)
           return account;
      }
      return null;
    }

    public boolean isSyncObjectDirty(SyncObject object) {
        return false; // na
    }

    public void setSyncObjectDirty(SyncObject object) {
        // na
    }

    public void clearSyncObjectDirty(SyncObject object) {
        // na
    }

    public int getSyncObjectCount() {
        return AccountDb.getAccounts().size();
    }

    public int getSyncVersion() {
        return 1;
    }

    public String getSyncName() {
        return ResourceBundle.getBundle(AuthenticatorResource.BUNDLE_ID, AuthenticatorResource.BUNDLE_NAME).getString(AuthenticatorResource.APP_NAME);
    }

    public String getSyncName(Locale locale) {
        return null;
    }

    public SyncConverter getSyncConverter() {
        return this;
    }

    public void beginTransaction() {

    }

    public void endTransaction() {
        /* not needed */
    }

    // OTASyncCapable methods
    // ---------------------------------------------------
    public SyncCollectionSchema getSchema() {
        return _schema;
    }

}
